/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package net.worlds;

import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.worlds.object.Camera;
import net.worlds.object.Editor;
import net.worlds.object.GameObject;
import net.worlds.object.Team;
import net.worlds.ui.UI;
import org.jbox2d.common.Vec2D;
import org.jbox2d.common.XForm;
import org.jbox2d.dynamics.Body;
import se.krka.kahlua.vm.LuaClosure;
import se.krka.kahlua.vm.LuaTableImpl;


public class Game {
    public Camera currentCamera;
    public int type = 0;
    private HashMap<String, World> worlds = new HashMap<String, World>(16);
    protected World mainWorld;
    protected World currentWorld;
    public boolean isPlaying = true;
    public boolean isTopDown = false;
    protected final Map<Integer, GameObject> objectIDs = Collections.synchronizedMap(new HashMap<Integer, GameObject>(Short.MAX_VALUE/2));
    protected final List<Integer> freeIDs = Collections.synchronizedList(new ArrayList<Integer>(Short.MAX_VALUE/2));
    protected final Map<Integer, Integer> changedIDs = Collections.synchronizedMap(new HashMap<Integer, Integer>(Short.MAX_VALUE/2));
    private HashMap<String, Team> teams = new HashMap<String, Team>(8);
    private Editor editor;
    public Game()
    {
        if(!(this instanceof NullGame))
        {
            try {
                UI.clearUndoList();
                editor = new Editor();
                for(int i = 0; i <= Short.MAX_VALUE; i++)
                    freeIDs.add(i);
                Main.scriptsStarted = false;
                World main = new World(WorldsSettings.MAX_OBJECTS);
                main.game = this;
                main.name = "main";
                mainWorld = main;
                main.create();
                worlds.put("main", main);
                currentWorld = main;
                if(Main.getScriptContext() != null)
                    Main.getScriptContext().setGame(main);
            } catch (WorldsException ex) {
                ex.printStackTrace();
            }
        }
    }
    public Game load(String script) throws WorldsException
    {
        if(!script.contains("|:;||||::"))
        {
            Main.getScriptContext().setGame(mainWorld);
            Main.getScriptContext().executeScript(Main.fixOldLevel(script));
        }
        else
        {
            String[] parts = script.split("\\|:;\\|\\|\\|\\|::");
            if(!parts[0].contains("setType") && Main.isEditor())
                Main.getEditorTop().setSideScrollingButtons(true);
            for(String p : parts)
            {
                String scriptpart = p.trim();
                String world = scriptpart.split("\n")[0].replace("--World: ", "");
                if(!world.isEmpty())
                {
                    if(!getWorlds().containsKey(world))
                        createWorld(world);
                    Main.getScriptContext().setGame(getWorld(world));
                    Main.getScriptContext().executeScript(scriptpart);
                }
            }
        }
        return this;
    }
    public void setMainWorld()
    {
        setCurrentWorld("main");
    }
    public World getMainWorld()
    {
        return mainWorld;
    }
    public World getCurrentWorld()
    {
        return currentWorld;
    }
    public World getWorld(String name)
    {
        return worlds.get(name);
    }
    public void destroy() throws WorldsException
    {
        if(Main.isServer())
            Main.getServer().stop();
        else if(Main.isClient())
            Main.getClient().disconnect();
        for(World w : worlds.values())
        {
            w.destroy();
        }
        if(UI.advancedToolsVisible)
        {
            UI.updatePropsTable();
        }
        Main.setLocalPlayer(null);
        if(!Main.noView && Main.getView() instanceof MainView)
            ((MainView)Main.getView()).followObject = null;
    }
    public void stop(boolean replicate)
    {
        if(replicate && Main.isBuildServer())
            Main.getServer().sendWorldStatus(0);
        if(replicate && Main.isBuildClient())
            Main.getClient().sendWorldStatus(0);
        isPlaying = false;
        for(World w : worlds.values())
        {
            w.stop();
        }
        if(Main.isEditor())
        {
            UI.repaintUI = true;
            try {
                Main.getEditorTop().playButton.setImage(AssetManager.loadImage("play.png", "play.png"));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    public void play(final boolean replicate)
    {
        if(replicate && Main.isBuildServer())
            Main.getServer().sendWorldStatus(1);
        if(replicate && Main.isBuildClient())
            Main.getClient().sendWorldStatus(1);
        isPlaying = true;
        if(Main.isEditor())
        {
            UI.repaintUI = true;
            try {
                Main.getEditorTop().playButton.setImage(AssetManager.loadImage("stop.png", "stop.png"));
            } catch (IOException ex) {
                throw new RuntimeException(ex);
            }
        }
        if(!Main.scriptsStarted)
            Main.runScripts();

        if(replicate)
        {
            final BodyPositions bpBefore = saveBodyPositions();
            final BodyPositions bpAfter = new BodyPositions();

            UI.action(new RedoRunnable()
            {

                @Override
                public Object run(boolean redo) throws WorldsException {
                    if(redo)
                        loadBodyPositions(bpAfter);
                    return null;
                }
            }, new UndoRunnable()
            {

                @Override
                public void run(Object obj) throws WorldsException {
                    bpAfter.set(saveBodyPositions());
                    loadBodyPositions(bpBefore);
                    stop(replicate);
                }
            }, false);
        }
    }
    public void toggleIsPlaying()
    {
        if(mainWorld == null || mainWorld.getRootObject() == null)
            return;
        if(isPlaying)
            stop(true);
        else
            play(true);
    }
    public void setCurrentWorld(String name)
    {
        try {
            if(!Main.noView)
            {
                UI.clearSelection(true);
                UI.clearDragParts();
            }
        } catch (WorldsException ex) {
            ex.printStackTrace();
        }
        currentWorld = worlds.get(name);
        net.worlds.object.World worldObj;
        try {
            worldObj = (net.worlds.object.World) currentWorld.getService("World");
            worldObj.setProp("BackgroundColor", worldObj.getProp("BackgroundColor"), true);
            worldObj.setProp("BackgroundImage", worldObj.getProp("BackgroundImage"), true);
        } catch (WorldsException ex) {
            Logger.getLogger(Game.class.getName()).log(Level.SEVERE, null, ex);
        }
        if(!Main.noView && currentWorld.getBackgroundImage() != null)
        {
            if(currentWorld.scaleBackground)
                ((MainView)Main.getView()).updateBackground();
            else
                ((MainView)Main.getView()).updatePaint(currentWorld);
        }
        try {
            if(Main.getLocalPlayer() == null && !Main.isClient())
                currentCamera = (Camera) currentWorld.getService("Camera");
            if(currentCamera != null)
            {
                if(!Main.noView)
                    Main.getView().setViewPropertiesFromCamera(currentCamera);
                if(currentCamera.getVec2DProp("Position").equals(Vec2D.ORIGIN))
                    currentCamera.zoomToCenter();
            }
            if(!Main.noView)
                Main.getView().getPartsTree().create(currentWorld.getRootObject());
            if(currentWorld.localPlayer != null)
                Main.setLocalPlayer(currentWorld.localPlayer, false);
            else if(currentWorld.localPlayer == null && Main.getLocalPlayer() != null)
                Main.setLocalPlayer(null, false);
            } catch (WorldsException ex) {
                ex.printStackTrace();
            }
    }
    public World createWorld(String name) throws WorldsException
    {
        World world = new World(WorldsSettings.MAX_OBJECTS);
        world.game = this;
        world.name = name;
        world.create();
        worlds.put(name, world);
        if(Main.isEditor())
            Main.getEditorTop().updateWorldMenu();
        return world;
    }
    public void destroyWorld(String name)
    {
        if(currentWorld == worlds.get(name))
        {
            setCurrentWorld("main");
        }
        worlds.remove(name);
    }
    public void runScripts()
    {
        for(World w : worlds.values())
        {
            w.runScripts();
        }
        Main.scriptsStarted = true;
    }
    public void initializeObjectIndex(Class c)
    {
        for(World w : worlds.values())
        {
            w.initializeObjectIndex(c);
        }
    }
    public void step() throws WorldsException
    {
        if(Main.isServer())
        {
            for(World w : worlds.values())
                w.step();
        }
        else
            currentWorld.step();
    }
    public boolean isIDTaken(GameObject obj, int id)
    {
        synchronized(objectIDs)
        {
            return objectIDs.get(new Integer(id)) != null && objectIDs.get(new Integer(id)) != obj;
        }
    }
    public void indexObject(GameObject obj, int oldID)
    {
        if(oldID != -1 && !freeIDs.contains(obj.getID()))
            synchronized(freeIDs)
            {
                freeIDs.add(new Integer(oldID));
            }
        if(obj.getID() != -1)
        {
            synchronized(freeIDs)
            {
                freeIDs.remove(new Integer(obj.getID()));
            }
            if(oldID != -1)
                synchronized(changedIDs)
                {
                    changedIDs.put(new Integer(oldID), obj.getID());
                }
        }
        synchronized(objectIDs)
        {
            if(oldID != -1)
                objectIDs.remove(new Integer(oldID));
            objectIDs.put(new Integer(obj.getID()), obj);
            //System.err.println(obj.getID()+" "+objectIDs.get(new Integer(32767)).getType());
        }
    }
    public GameObject getByID(int id)
    {
        synchronized(objectIDs)
        {
            if(objectIDs.get(new Integer(id)) == null && changedIDs.get(new Integer(id)) != null)
                id = changedIDs.get(new Integer(id));
            return objectIDs.get(new Integer(id));
        }
    }
    public GameObject getByOldID(int id)
    {
        synchronized(changedIDs)
        {
            if(changedIDs.containsKey(id))
                id = changedIDs.get(new Integer(id));
            return getByID(id);
        }
    }
    public int getFreeID()
    {
        synchronized(freeIDs)
        {
            return freeIDs.get(0);
        }
    }
    public HashMap<String, World> getWorlds()
    {
        return worlds;
    }
    public void setWorlds(Object[] newWorlds)
    {
        List<Object> newWorldsList = Arrays.asList(newWorlds);
        for(final String worldName : worlds.keySet())
        {
            if(!newWorldsList.contains(worldName))
            {
                Main.addStepTask(new Runnable()
                {
                    public void run() {
                        destroyWorld(worldName);
                        System.out.println("Removed the world "+worldName);
                    }
                });
            }
        }
        for(Object obj : newWorlds)
        {
            String[] parts = obj.toString().split(" ");
            if(parts.length == 2)
            {
                String worldName = parts[0];
                String[] dimensions = parts[1].replace("[", "").replace("]", "").split(",");
                int width = Integer.parseInt(dimensions[0]);
                int height = Integer.parseInt(dimensions[1]);
                if(!worlds.keySet().contains(worldName))
                    try {
                    World world = createWorld(worldName);
                    world.setup(width, height);
                    world.buildBoundaries();
                    System.out.println("Created the world "+worldName);
                    } catch (WorldsException ex) {
                        System.err.println("Error creating world: "+ex.toString());
                    }
            }
        }
        if(!Main.noView && Main.getEditorTop() != null)
            Main.getEditorTop().updateWorldMenu();
    }
    public void serialize(OutputStream out, boolean urlencode) throws IOException, WorldsException
    {
        Writer w = new OutputStreamWriter(out);
        for(String worldName : worlds.keySet())
        {
            w.write("--World: "+worldName+"\n");
            w.flush();
            worlds.get(worldName).serialize(out, urlencode);
            w.write("|:;||||::\n");
            w.flush();
        }
    }
    public String serializeToString() throws IOException, WorldsException
    {
        Writer w = new StringWriter();
        for(String worldName : worlds.keySet())
        {
            w.write("--World: "+worldName+"\n");
            w.flush();
            String world = worlds.get(worldName).serializeToString();
            w.write(world);
            w.write("|:;||||::\n");
            w.flush();
        }
        return w.toString();
    }
    public LuaTableImpl getObjectsByType(Class clazz)
    {
        LuaTableImpl t = new LuaTableImpl();
        int index = 1;
        for(World w : worlds.values())
        {
            synchronized(w.getObjectsByType(clazz))
            {
                for(GameObject obj : w.getObjectsByType(clazz))
                {
                    t.rawset(index, obj);
                    index++;
                }
            }
        }
        return t;
    }
    public void addTeam(Team t)
    {
        String name = null;
        try {
            name = t.getStringProp("Name");
        } catch (WorldsException ex) {
        }
        if(!teams.containsKey(name))
            teams.put(name, t);
    }
    public void removeTeam(String t)
    {
        teams.remove(t);
    }
    public HashMap<String, Team> getTeams()
    {
        return teams;
    }
    public Editor getEditorObject()
    {
        return editor;
    }
    public void addActionButton(LuaClosure c, String i, String tt)
    {
        if(Main.getEditorOverlay() != null)
            Main.getEditorOverlay().addActionButton(c, i, tt);
    }
    public void clearActionButtons()
    {
        if(Main.getEditorOverlay() != null)
            Main.getEditorOverlay().clearActionButtons();
    }
    public void setMouseMode(int m)
    {
        if(Main.getEditorOverlay() != null)
            editor.getEvent("MouseModeChanged").fire(new Object[]{m});
    }
    public class BodyPositions
    {
        public HashMap<World, HashMap<Body, Vec2D>> positions = new HashMap<World, HashMap<Body, Vec2D>>();
        public HashMap<World, HashMap<Body, Float>> rotation = new HashMap<World, HashMap<Body, Float>>();
        public HashMap<World, HashMap<Body, Vec2D>> velocities = new HashMap<World, HashMap<Body, Vec2D>>();
        public void set(BodyPositions bp)
        {
            positions = bp.positions;
            rotation = bp.rotation;
            velocities = bp.velocities;
        }
    }
    public BodyPositions saveBodyPositions()
    {
        final HashMap<World, HashMap<Body, Vec2D>> positions = new HashMap<World, HashMap<Body, Vec2D>>();
        final HashMap<World, HashMap<Body, Float>> rotation = new HashMap<World, HashMap<Body, Float>>();
        final HashMap<World, HashMap<Body, Vec2D>> velocities = new HashMap<World, HashMap<Body, Vec2D>>();

        for(World w : worlds.values())
        {
            w.play();
            HashMap<Body, Vec2D> p = new HashMap<Body, Vec2D>();
            HashMap<Body, Float> r = new HashMap<Body, Float>();
            HashMap<Body, Vec2D> v = new HashMap<Body, Vec2D>();
            positions.put(w, p);
            rotation.put(w, r);
            velocities.put(w, v);
           for(Body b = w.getBodyList(); b.m_next != null; b = b.m_next)
           {
               p.put(b, b.getPosition());
               r.put(b, b.getAngle());
               v.put(b, b.getLinearVelocity());
           }
        }
        BodyPositions bp = new BodyPositions();
        bp.positions = positions;
        bp.rotation = rotation;
        bp.velocities = velocities;
        return bp;
    }
    private void loadBodyPositions(BodyPositions bp)
    {
    for(World w : worlds.values())
    {
        for(Body b : bp.positions.get(w).keySet())
        {
           b.setXForm(bp.positions.get(w).get(b), bp.rotation.get(w).get(b));
           b.setLinearVelocity(bp.velocities.get(w).get(b));
        }
    }
    }
}
